RxAndroid 新型异步API(译)

原文链接:RxAndroid’s New Async API

原文作者:Zac Sweers

译文出自:Dimon’s Program Basement

译者:Dimon

"RxAndroid meets VSYNC rubber" — Icon used with permission from Ray Wenderlich

RxAndroid 2.1.0有一个新的API:

AndroidSchedulers#from(Looper looper, boolean async)

这新的async参数将影响Android APIs 16 及其以上版本,如果你的APP高度依赖RxJava+RxAndroid,将这个参数设置为true将能够显著提升 UI 性能表现。

由于RxAndroid的主要版本与RxJava绑定,我们不希望在次要版本中默默地引入重要的行为更改,所以这个API默认不启用。

要安装它,你可以使用RxAndroidPlugins设置此API自定义的scheduler

EDIT:由于类加载问题,以下代码在setInitMainThreadSchedulerHandler上有一些错误。为了避免初始化默认值,你应该在传入的 lambda/callable 中(inside)调用AndroidSchedulers.from(...),而不是在之前就调用;

Kotlin:

1
2
3
4
5
6
//Before
val asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true)
RxAndroidPlugins.setInitMainThreadSchedulerHandler { asyncMainThreadScheduler }

//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler { asyncMainThreadScheduler }
1
2
3
4
5
6
7
8
9
//Correct usage
RxAndroidPlugins.setInitMainThreadSchedulerHandler {
AndroidSchedulers.from(Looper.getMainLooper(), true)
}

//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler {
AndroidSchedulers.from(Looper.getMainLooper(), true)
}

Java:

1
2
3
4
5
6
//Before
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -> asyncMainThreadScheduler);

//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> asyncMainThreadScheduler);
1
2
3
4
5
6
7
8
9
10
11
//Correct usage
RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -> {
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
return asyncMainThreadScheduler;
});

//Or if the default scheduler is already initialiazed
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> {
Scheduler asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), true);
return asyncMainThreadScheduler;
});

正文

这是一个很长的时间。RxAndroid中使用主线程历来都是使用Handler#post()去调度新的Message们。这通常是需要付出代价:默认情况下这个的遵循VSYNC锁定并且将导致等待直到下一帧运行。对于通过post()的每一个发射,这都将是一个高达16ms的延迟。如果你已经在主线程上,这更是会加剧这种情况的发生(Ray Ryan曾经在一个出色的演讲上讲到过这个问题)。

因此,在2015年,在Jake WhartonRxBinding项目中围绕“快速路径(fastpath)”展开了讨论:主线程调度程序如果已经在主线程上并且避免VSYNC成本,则可以立即运行工作。之后这个讨论转移到RxAndroid的一个提案上,并且得到了很好的社区反馈,但是由于担心可能通过直接运行事件和冒着死锁的风险来竞争系统的事件,导致从未达成过共识;所以它受到了抨击,但对于使用者们仍旧是一个摩擦点,并且在RxAndroid2.x中提交了几个issues

很快到了2017年年初,在Uber里我们决定尝试在内部进行re-hash,并且发现它基本上是稳定的。我们确实时而会看到死锁,但它们很难被发现,并且看起来很罕见,值得我们换取性能上的提升。上线一年后,我们决定尝试上游化我们的实现,并且重新开启讨论。这一次,Android框架工程师(Adam Powell)看到了这个讨论,并且指出我们使用MessageHandler的异步API。这个API允许消息绕过VSYNC锁定,同时仍让框架在其looper中安全地处理所有调度,这正是我们一直想要的!

连线API

虽然异步API自API 16以来就已存在,但它们已经通过@hide隐藏在SDK中。在API22中,Message#setAsynchronous()方法变成了public。在API28中,有一个新的Handler.createAsync()工厂API,它默认将它处理的所有消息设置为异步。由于这些API为操作系统中一些最关键的部分提供了动力,因此它们不太可能被更改,并且应该可以安全访问。这里有点乐趣。

API 22+

使用上述公共setAsynchronous()方法。

API[16-21]

仍然使用setAsynchronous(),但我们抑制了lint错误,表明它只有22+。为了避免删除/更改内部API的任何(不太可能的)OEM情况,我们 try/catch from()调度程序工厂中的快速Message#setAsynchronous()方法调用,以确保它在运行时存在,捕获NoSuchMethodError,如果它丢失并且优雅地回到标准的非异步消息传递。它不是反射,因为我们知道这将在运行时,因为该方法确实存在于运行时。

API < 16

没有行为更改,因为异步API不存在,所以使用标准的非异步消息传递。

就是这些!我希望在使用主线程调度程序时,这可以让开发人员更加安心。请试一试并报告任何问题。